<html>
<head>
  <title>16.迭代器（Iterator）模式</title>
  <basefont face="微软雅黑" size="2" />
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  <meta name="exporter-version" content="YXBJ Windows/602628 (zh-CN, DDL); Windows/10.0.0 (Win64); EDAMVersion=V2;"/>
  <style>
    body, td {
      font-family: 微软雅黑;
      font-size: 10pt;
    }
  </style>
</head>
<body>
<a name="24192"/>
<h1>16.迭代器（Iterator）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/8/31 15:18</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/1/20 5:45</i></td></tr>
<tr><td><b>作者：</b></td><td><i>Long Life Student</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-weight: bold;">一、基本描述</span></div><ul><li><div>提供一种方法顺序访问一个聚合对象中的各个元素，而又不需要暴露该对象每部表示。</div></li><li><div>又称为游标模式。</div></li><li><div>迭代器模式的关键思想是将对列表的访问和遍历从列表对象中分离出来并放入一个迭代器对象中。迭代器类定义了一个访问该列表元素的接口，迭代器类对象负责跟踪当前的元素，即它知道哪些元素已经遍历过了。</div></li><li><div>迭代器中有几个基本的操作：</div></li></ul><div style="margin-left: 40px;">1）CurrentItem——返回列表中的当前元素</div><div style="margin-left: 40px;">2）First——操作初始化迭代器，使当前元素指向列表的第一个元素</div><div style="margin-left: 40px;">3）Next——操作将当前元素指针向前推进一步，指向下一个元素</div><div style="margin-left: 40px;">4）IsDone——检查是否已越过最后一个元素，也就是完成了这次遍历<br/>
java.util.Iterator中定义了不同的基本操作：<br/>
1）next()</div><div style="margin-left: 40px;">2) hasNext()</div><div style="margin-left: 40px;">3) remove()</div><ul><li><div>将遍历机制与列表对象分离使我们可以定义不同的迭代器来实现不同的遍历策略，而无需在列表接口中列举它们。</div></li><li><div>迭代器和列表是耦合在一起的，而且客户对象必须知道遍历的是一个列表而不是其他聚合结构。</div></li><li><div>以下情况可以使用迭代器模式：</div></li></ul><div style="margin-left: 40px;">1）访问一个聚合对象的内容而无须暴露它的内部表示</div><div style="margin-left: 40px;">2）支持对聚合对象的多种遍历</div><ul><li><div>迭代器根据谁控制迭代可以分为外部迭代器（由客户来控制迭代）和内部迭代器（由迭代器控制迭代）。</div></li><li><div>当需要访问一个聚焦对象，而且不管这些对象是什么都需要遍历的时候，就应该考虑用迭代器模式。</div></li><li><div>迭代器模式为遍历不同的聚集结构提供如开始、下一个、是否结束、当前哪一项等统一的接口。</div></li><li><div>当前，迭代器模式实用价值远不如学习价值大，因为现在高级编程语言如C#、Java等本身已经把这个模式做在语言中了。迭代器模式目前已经是一个没落的模式，作为一个最基础的工具，基本上没有人会单独写一个迭代器。</div></li><li><div>迭代器模式分离了集合对象的遍历行为，抽象出一个迭代器类来负责，这样既可以做到不暴露集合的内部结构，又可以让外部代码透明的访问集合内部的数据。</div></li><li><div>迭代器模式在访问数组、集合、列表 等数据时，尤其是在数据库数据操作时，是非常普遍的应用，但由于它太普遍了，所以各种高级语言都对它进行了封装，所以反而给人感觉此模式本身不太常用了。</div></li><li><div>迭代器模式提供了遍历容器的方便性，容器只要管理增减元素就行了，需要遍历时交由迭代器进行</div></li><li><div>简单的说，迭代器就类似于一个数据库中的游标，可以在一个容器内上下翻滚，遍历所有它需要查看的元素。</div></li><li><div>聚合类（Aggregate Class）是可以存储多个成员对象（元素）的类，对应的对象称为聚合对象。</div></li><li><div>通过引入迭代器，客户端无需了解聚合对象的内部结构即可实现对聚合对象中成员的遍历，还可以根据需要很方便的增加新的遍历方式。</div></li><li><div>将聚合类中负责遍历数据的方法提取出来， 封装到专<span style="font-size: unset; color: unset; font-family: unset;">门的类中， 实现数据存储和数据遍历分离， 无须暴露聚合类的内部属性即可对其进行操作，</span><span style="font-size: unset; color: unset; font-family: unset;">这正是迭代器模式的意图。</span></div></li><li><div>在迭代器模式结构中包含聚合和迭代器两个层次结构， 考虑到系统的灵活性和可扩展性， 在<span style="font-size: unset; color: unset; font-family: unset;">迭代器模式中应用了工厂方法模式。</span></div></li><li><div>在迭代器模式中， 提供了一个外部的迭代器来对聚合对象进行访问和遍历， 迭代器定义了一<span style="font-size: unset; color: unset; font-family: unset;">个访问该聚合元素的接口， 并且可以跟踪当前遍历的元素， 了解哪些元素已经遍历过而哪些</span><span style="font-size: unset; color: unset; font-family: unset;">没有。 迭代器的引入， 将使得对一个复杂聚合对象的操作变得简单。</span></div></li><li><div>在迭代器模式中应用了工厂方法<span style="font-size: unset; color: unset; font-family: unset;">模式， 抽象迭代器对应于抽象产品角色， 具体迭代器对应于具体产品角色， 抽象聚合类对应</span><span style="font-size: unset; color: unset; font-family: unset;">于抽象工厂角色， 具体聚合类对应于具体工厂角色。</span></div></li><li><div>在抽象迭代器中声明了用于遍历聚合对象中所存储元素的方法， 在<span style="font-size: unset; color: unset; font-family: unset;">具体迭代器中将实现抽象迭代器声明的遍历数据的方法。</span></div></li><li><div>需<span style="font-size: unset; color: unset; font-family: unset;">要注意的是抽象迭代器接口的设计非常重要， 一方面需要充分满足各种遍历操作的要求，</span><span style="font-size: unset; color: unset; font-family: unset;">尽量为各种遍历方法都提供声明， 另一方面又不能包含太多方法， 接口中方法太多将给子类</span><span style="font-size: unset; color: unset; font-family: unset;">的实现带来麻烦。（可以采用的策略：</span>在设计时要考虑全面， 避<span style="font-size: unset; color: unset; font-family: unset;">免之后修改接口。同时，使用抽象类来设计抽象迭代器，在抽象类中为每一个方法提供一个空的默认实现</span><span style="font-size: unset; color: unset; font-family: unset;">）</span></div></li><li><div>为了能够让迭代器可以访问到聚合对象中的数据，有两种实现的方式：</div></li></ul><div style="margin-left: 40px;">1）关联关系：在具体迭代器中维持一个对具体聚合对象的引用，以便访问存储在聚合对象中的数据。</div><div style="margin-left: 40px;">2）将迭代器类设计为聚合类的内部类，JDK中的迭代器类就是通过这种方法实现的。</div><ul><li><div>迭代器模式是一种使用频率非常高的设计模式，通过引入迭代器可以将数据的遍历功能从聚合对象中分离出来，聚合对象只负责存储数据，而遍历由迭代器完成。</div></li><li><div>迭代器让我们能够遍历聚合中的每个元素，而不会去强迫聚合必须提供方法，让我们在它的元素中游走。我们也可以在聚合的外面实现迭代器，换句话说，我们封装了遍历。</div></li><li><div>在设计中使用迭代器的影响是明显的：</div></li><ul><li><div>如果有一个统一的方法访问聚合中的每一个对象，就可以编写多态的代码和这些聚合搭配、使用</div></li><li><div>迭代器模式把在元素之间游走的责任交给迭代器，而不是聚合对象。这不仅让聚合的接口和实现变得简洁，也可以让聚合更专注在它所应该专注的事情上面（也就是管理对象集合），而不必去理会遍历的事情。让责任各得其所。</div></li></ul><li><div>迭代器的实现</div></li><ul><li><div>内部迭代器：有迭代器自行控制在元素间的游走，必须将操作传入迭代器，告诉迭代器在游走的过程中需要做什么事情，客户无法控制遍历的过程。</div></li><li><div>外部迭代器：一般常见的迭代器，客户通过next()取得下一个元素。</div></li></ul></ul><div><span style="font-weight: bold;">二、图例</span></div><div><img src="16.迭代器（Iterator）模式_files/Image.png" type="image/png" data-filename="Image.png" width="549"/></div><ul><li><div>Iterator（迭代器）：迭代器定义访问和遍历元素的接口。声明了用于遍历元素的方法。是迭代抽象类，用于定义得到开始对象（First）、得到下一个对象（Next）、判断是否到结尾（IsDone）、当前对象（CurrentItem）等抽象方法，统一接口。</div></li><li><div>ConcreteIterator（具体迭代器）：具体迭代器，实现迭代器接口。对该聚合遍历时跟踪当前位置。主要实现抽象迭代器接口，完成对聚合对象的遍历，同时在具体迭代器中通过游标来记录在聚合对象中所处的当前位置，在具体实现时，游标通常是一个表示位置的非负整数。</div></li><li><div>Aggregate（聚合）：聚合定义创建相应的迭代器对象的接口。聚合抽象类，存储和管理元素对象，声明了一个CreateIterator方法用于创建一个迭代器对象，充当抽象迭代器工厂角色。</div><div>有一个共同的接口供所有的聚合使用，这对客户代码是很方便的，将客户代码从集合对象的实现解耦了。</div></li><li><div>ConcreteAggregate（具体聚合）：具体聚合持有一个对象的集合，并实现创建相应迭代器的接口，该操作返回ConcreteIterator的一个适当的实例。</div></li><li><div>协作：</div></li><ul><li><div>ConcreteIterator跟踪聚合中的当前对象，并能够计算出待遍历的后继对象。</div></li><li><div>每一个具体聚合都要负责实例化一个具体迭代器，此迭代器能够遍历对象的集合。</div></li></ul></ul><div><span style="font-weight: bold;">三、优缺点</span></div><div><span style="font-weight: bold;">优点</span></div><ul><li><div><span style="font-weight: bold;">支持以不同的方式遍历一个聚合</span>。复杂的聚合可用多种方式进行遍历。迭代器模式使得改变遍历算法变的很容易：仅需用一个不同的迭代器的实例代替原先的实例即可。也可以自己定义迭代器的子类以支持新的遍历。</div></li><li><div><span style="font-weight: bold;">简化了聚合的接口</span>。有了迭代器的遍历接口，聚合本身就不再需要类似的遍历接口了，这样就简化了聚合的接口。</div></li><li><div><span style="font-weight: bold;">在同一个聚合上可以有多个遍历</span>。每个迭代器保持它自己的遍历状态，因此可以同时进行多个遍历。</div></li><li><div><span style="font-weight: bold;">满足“开闭原则”的要求</span>。在迭代器模式中，由于引入了抽象层，增加新的聚合类和迭代器类都很方便，无需修改原有代码。</div></li></ul><div><span style="font-weight: bold;">缺点</span></div><ul><li><div>由于迭代器模式将存储数据和遍历数据的职责分离， 增加新的聚合类需要对应增加新的迭<span style="font-size: unset; color: unset; font-family: unset;">代器类， 类的个数成对增加， 这在一定程度上增加了系统的复杂性。</span></div></li><li><div>抽象迭代器的设计难度较大， 需要充分考虑到系统将来的扩展，在自定义迭代器时， 创建一个考虑全面<span style="font-size: unset; color: unset; font-family: unset;">的抽象迭代器并不是件很容易的事情。</span></div></li></ul><div><span style="font-weight: bold;">四、可能的改进</span></div><ul><li><div>可以采用<a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/iterator/JavaIterator">内置类</a>的方法来实现迭代器类，即迭代器类是聚合类的内置类。</div></li></ul><div><span style="font-weight: bold;">五、个人理解</span></div><ul><li><div>迭代器模式的应用极其广泛，但一般都内置到语言的基础设施当中，基本没有机会去自己开发一个迭代器。</div></li><li><div>一般迭代器要同工厂方法模式联用。</div></li><li><div>实际上迭代器并不复杂，甚至可以说非常简单。</div></li></ul><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br/></div></div><div><br/></div></span>
</div></body></html> 